/*
 * This file is part of Jeve.

    Jeve is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Jeve is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    Copyright 2007 Jochen Bedersdorfer (beders@yahoo.com)
 *
 */
package de.monoid.eve;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Main interface to the EVE Web API
 * @author beders
 */
public class EveInterface {

  private UserID userID;

  private APIKey apiKey;

  /**
   * Create the eve interface
   * @param anUserID the user id
   * @param anAPIKey the api key
   */
  EveInterface(UserID anUserID, APIKey anAPIKey) {
    userID = anUserID;
    apiKey = anAPIKey;
  }

  /** Return the raw XML data for the given character id.
   * Don't forget to close the stream.
   * @param aCharacter the character to get info for
   * @return the XML document or null if no info is available
   */
  Document getCharData(Character aCharacter) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      return parse(connect(requestData, "/char/CharacterSheet.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /** Return the skill data for the given character id. 
   * @param aCharacter the character to get the info for
   * @return the XML document of the current skills in training*/
  Document getSkillsInTraining(Character aCharacter) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      return parse(connect(requestData, "/char/SkillInTraining.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /** Return the list of all available skills.
   * @return the XML document of the result, null if an IOException occured
  */
  Document getAllSkills() {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      return parse(connect(requestData, "/eve/SkillTree.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;

  }

  /**
   * Return the list of all reference type used in the eve XML.
   * @return the XMl document, null if an IOException occured
   */
  Document getRefTypes() {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      return parse(connect(requestData, "/eve/RefTypes.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * Return the soverignty information of the map as XML document.
   * @return the XML document, null if an IOException occured
   */
  Document getSovereignty() {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      return parse(connect(requestData, "/map/Sovereignty.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * Get the last 100 entries of the wallet journal of this character
   * @param aCharacter the character to use
   * @return the XML document or null if an error occured
   */
  Document getCharWalletJournal(Character aCharacter) {
    return getCharWalletJournal(aCharacter, null);
  }

  /**
   * Get entries from the wallet journal of this character.
  * @param aCharacter the character to use
  * @param beforeRefID the latest ref id, taken from the list of wallet entries
  * @return the XML document or null if an error occured
  */
  Document getCharWalletJournal(Character aCharacter, String beforeRefID) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      setParameter(requestData, "accountKey", "1000");
      if (null != beforeRefID) {
        setParameter(requestData, "beforeRefID", beforeRefID);
      }
      return parse(connect(requestData, "/char/WalletJournal.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }
  
  Document getCorpWalletJournal(Character aCharacter, int accountNr, String beforeRefID) {
    // TODO range check account nr
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      setParameter(requestData, "accountKey", "" + accountNr);
      if (null != beforeRefID) {
        setParameter(requestData, "beforeRefID", beforeRefID);
      }
      return parse(connect(requestData, "/corp/WalletJournal.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }
  
  /**
   * Get entries from the wallet transactions of this character.
   * @param aCharacter the character to use
   * @return the XML document or null if an error occured
   */
   Document getCharWalletTrx(Character aCharacter) {
     return getCharWalletTrx(aCharacter, null);
   }
  
  /**
  * Get entries from the wallet transactions of this character.
  * @param aCharacter the character to use
  * @param beforeTrxID the latest trx id, taken from the list of wallet entries
  * @return the XML document or null if an error occured
  */
  Document getCharWalletTrx(Character aCharacter, String beforeTrxID) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      setParameter(requestData, "accountKey", "1000");
      if (null != beforeTrxID) {
        setParameter(requestData, "beforeTransID", beforeTrxID);
      }
      return parse(connect(requestData, "/char/WalletTransactions.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * Get entries from the wallet transactions of the corporation of this character.
  * @param aCharacter the corporation of this character will be used
   * @param accountNr the account nr (1000-10006)
  * @param beforeTrxID the latest trx id, taken from the list of wallet entries
  * @return the XML document or null if an error occured
  */
  Document getCorpWalletTrx(Character aCharacter, int accountNr, String beforeTrxID) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      setParameter(requestData, "accountKey", "" + accountNr);
      if (null != beforeTrxID) {
        setParameter(requestData, "beforeTransID", beforeTrxID);
      }
      return parse(connect(requestData, "/corp/WalletTransactions.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * Get the balance overview for this character.
   * @param aCharacter the character to use
   * @return the XML document of the balance
   */
  Document getCharAccountBalance(Character aCharacter) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      return parse(connect(requestData, "/char/AccountBalance.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  /**
   * Get the balance overview for the corp of this character.
   * @param aCharacter the character to use (i.e. the corp of this character)
   * @return the XML document of the balance
   */
  Document getCorpAccountBalance(Character aCharacter) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      return parse(connect(requestData, "/corp/AccountBalance.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }
  
  
  /**
   * Get the member tracking information of the corp of this character.
   * @param aCharacter the character to use
   * @return the resulting XML document, null if an error occurs.
   */
  Document getCorpMembers(Character aCharacter) {
    try {
      java.lang.StringBuilder requestData = new java.lang.StringBuilder();
      authorize(requestData);
      setParameter(requestData, "characterID", aCharacter.getID());
      return parse(connect(requestData, "/corp/MemberTracking.xml.aspx"));
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return null;
  }
  
  private Document parse(InputStream aStream) {
    InputSource is = new InputSource(aStream);
    try {
      Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
      return d;
    } catch (SAXException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (ParserConfigurationException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      try {
        aStream.close();
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    return null;
  }

  /** Add the authorization data to access the web app.
   * 
   * @param requestData the string builder to use
   */
  private void authorize(StringBuilder requestData) {
    userID.encode(requestData);
    requestData.append('&');
    apiKey.encode(requestData);
    requestData.append('&');
  }

  /**
   * Make a POST request, providing the request data
   * @param requestData the request data
   * @param filePart the "function" to call, expressed as a path
   * @return the raw input stream
   * @throws IOException if the web site is not available
   * @throws MalformedURLException if the filePart is malformed 
   */
  private InputStream connect(StringBuilder requestData, String filePart) throws IOException,
      MalformedURLException {
    java.net.URL url = new java.net.URL("http://api.eve-online.com" + filePart);
    java.net.URLConnection conn = url.openConnection();
    conn.setDoOutput(true);
    java.io.OutputStreamWriter wr = new java.io.OutputStreamWriter(conn.getOutputStream());
    System.out.println(requestData.toString());
    wr.write(requestData.toString());
    wr.close();
    // TODO - improve
    return conn.getInputStream();
  }

  private void setParameter(StringBuilder requestData, String aKey, String aValue)
      throws UnsupportedEncodingException {
    if (requestData.charAt(requestData.length() - 1) != '&')
      requestData.append('&');
    requestData.append(aKey).append('=').append(java.net.URLEncoder.encode(aValue, "UTF-8"));
  }

}
